/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.eclipse.imagen.media.util;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.ColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderContext;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Map;
import java.util.Vector;
import org.eclipse.imagen.DeferredData;
import org.eclipse.imagen.JAI;
import org.eclipse.imagen.KernelJAI;
import org.eclipse.imagen.PixelAccessor;
import org.eclipse.imagen.PlanarImage;
import org.eclipse.imagen.RasterAccessor;
import org.eclipse.imagen.UnpackedImageData;
import org.eclipse.imagen.util.ImagingException;
import org.eclipse.imagen.util.ImagingListener;

public final class ImageUtil {

    /** The minimum value of a float. */
    private static final float FLOAT_MIN = -Float.MAX_VALUE;

    /** The counter for images that use the method generateID to create a UID. */
    private static long counter;

    /** A constant used to extract a byte from a short or an int. */
    public static final int BYTE_MASK = 0xFF;

    /** A constant used to extract an unsigned short from an int. */
    public static final int USHORT_MASK = 0xFFFF;

    /** Clamps a number to the range supported by byte data type. */
    public static final byte clampByte(int in) {
        return (in > 0xFF ? (byte) 0xFF : (in >= 0 ? (byte) in : (byte) 0));
    }

    /** Clamps a number to the range supported by unsigned short data type. */
    public static final short clampUShort(int in) {
        return (in > 0xFFFF ? (short) 0xFFFF : (in >= 0 ? (short) in : (short) 0));
    }

    /** Clamps a number to the range supported by short data type. */
    public static final short clampShort(int in) {
        return (in > Short.MAX_VALUE ? Short.MAX_VALUE : (in >= Short.MIN_VALUE ? (short) in : Short.MIN_VALUE));
    }

    /** Clamps a number to the range supported by integer data type. */
    public static final int clampInt(long in) {
        return (in > Integer.MAX_VALUE ? Integer.MAX_VALUE : (in >= Integer.MIN_VALUE ? (int) in : Integer.MIN_VALUE));
    }

    /** Clamps a number to the range supported by float data type. */
    public static final float clampFloat(double in) {
        return (in > Float.MAX_VALUE ? Float.MAX_VALUE : (in >= FLOAT_MIN ? (float) in : FLOAT_MIN));
    }

    /** Clamps and rounds a number to the range supported by byte data type. The input number is float. */
    public static final byte clampRoundByte(float in) {
        return (in > 0xFF ? (byte) 0xFF : (in >= 0 ? (byte) (in + 0.5F) : (byte) 0));
    }

    /** Clamps and rounds a number to the range supported by byte data type. The input number is double. */
    public static final byte clampRoundByte(double in) {
        return (in > 0xFF ? (byte) 0xFF : (in >= 0 ? (byte) (in + 0.5) : (byte) 0));
    }

    /** Clamps and rounds a number to the range supported by unsigned short data type. The input number is float. */
    public static final short clampRoundUShort(float in) {
        return (in > 0xFFFF ? (short) 0xFFFF : (in >= 0 ? (short) (in + 0.5F) : (short) 0));
    }

    /** Clamps and rounds a number to the range supported by unsigned short data type. The input number is double. */
    public static final short clampRoundUShort(double in) {
        return (in > 0xFFFF ? (short) 0xFFFF : (in >= 0 ? (short) (in + 0.5) : (short) 0));
    }

    /** Clamps and rounds a number to the range supported by short data type. The input number is float. */
    public static final short clampRoundShort(float in) {
        return (in > Short.MAX_VALUE
                ? Short.MAX_VALUE
                : (in >= Short.MIN_VALUE ? (short) Math.floor(in + 0.5F) : Short.MIN_VALUE));
    }

    /** Clamps and rounds a number to the range supported by short data type. The input number is double. */
    public static final short clampRoundShort(double in) {
        return (in > Short.MAX_VALUE
                ? Short.MAX_VALUE
                : (in >= Short.MIN_VALUE ? (short) Math.floor(in + 0.5) : Short.MIN_VALUE));
    }

    /** Clamps and rounds a number to the range supported by integer data type. The input number is float. */
    public static final int clampRoundInt(float in) {
        return (in > Integer.MAX_VALUE
                ? Integer.MAX_VALUE
                : (in >= Integer.MIN_VALUE ? (int) Math.floor(in + 0.5F) : Integer.MIN_VALUE));
    }

    /** Clamps and rounds a number to the range supported by integer data type. The input number is double. */
    public static final int clampRoundInt(double in) {
        return (in > Integer.MAX_VALUE
                ? Integer.MAX_VALUE
                : (in >= Integer.MIN_VALUE ? (int) Math.floor(in + 0.5) : Integer.MIN_VALUE));
    }

    /** Clamps a positive number to the range supported by byte data type. */
    public static final byte clampBytePositive(int in) {
        return (in > 0xFF ? (byte) 0xFF : (byte) in);
    }

    /** Clamps a negative number to the range supported by byte data type. */
    public static final byte clampByteNegative(int in) {
        return (in < 0 ? (byte) 0 : (byte) in);
    }

    /** Clamps a positive number to the range supported by unsigned short data type. */
    public static final short clampUShortPositive(int in) {
        return (in > 0xFFFF ? (short) 0xFFFF : (short) in);
    }

    /*
     * Clamps a negative number to the range supported by
     * unsigned short data type.
     */
    public static final short clampUShortNegative(int in) {
        return (in < 0 ? (short) 0 : (short) in);
    }

    public static final void copyRaster(RasterAccessor src, RasterAccessor dst) {
        int srcPixelStride = src.getPixelStride();
        int srcLineStride = src.getScanlineStride();
        int[] srcBandOffsets = src.getBandOffsets();

        int dstPixelStride = dst.getPixelStride();
        int dstLineStride = dst.getScanlineStride();
        int[] dstBandOffsets = dst.getBandOffsets();

        int width = dst.getWidth() * dstPixelStride;
        int height = dst.getHeight() * dstLineStride;
        int bands = dst.getNumBands();

        switch (dst.getDataType()) {
            case DataBuffer.TYPE_BYTE:
                byte[][] bSrcData = src.getByteDataArrays();
                byte[][] bDstData = dst.getByteDataArrays();

                for (int b = 0; b < bands; b++) {
                    byte[] s = bSrcData[b];
                    byte[] d = bDstData[b];

                    int heightEnd = dstBandOffsets[b] + height;

                    for (int dstLineOffset = dstBandOffsets[b], srcLineOffset = srcBandOffsets[b];
                            dstLineOffset < heightEnd;
                            dstLineOffset += dstLineStride, srcLineOffset += srcLineStride) {

                        int widthEnd = dstLineOffset + width;

                        for (int dstPixelOffset = dstLineOffset, srcPixelOffset = srcLineOffset;
                                dstPixelOffset < widthEnd;
                                dstPixelOffset += dstPixelStride, srcPixelOffset += srcPixelStride) {

                            d[dstPixelOffset] = s[srcPixelOffset];
                        }
                    }
                }
                break;

            case DataBuffer.TYPE_USHORT:
            case DataBuffer.TYPE_SHORT:
                short[][] sSrcData = src.getShortDataArrays();
                short[][] sDstData = dst.getShortDataArrays();

                for (int b = 0; b < bands; b++) {
                    short[] s = sSrcData[b];
                    short[] d = sDstData[b];

                    int heightEnd = dstBandOffsets[b] + height;

                    for (int dstLineOffset = dstBandOffsets[b], srcLineOffset = srcBandOffsets[b];
                            dstLineOffset < heightEnd;
                            dstLineOffset += dstLineStride, srcLineOffset += srcLineStride) {

                        int widthEnd = dstLineOffset + width;

                        for (int dstPixelOffset = dstLineOffset, srcPixelOffset = srcLineOffset;
                                dstPixelOffset < widthEnd;
                                dstPixelOffset += dstPixelStride, srcPixelOffset += srcPixelStride) {

                            d[dstPixelOffset] = s[srcPixelOffset];
                        }
                    }
                }
                break;

            case DataBuffer.TYPE_INT:
                int[][] iSrcData = src.getIntDataArrays();
                int[][] iDstData = dst.getIntDataArrays();

                for (int b = 0; b < bands; b++) {
                    int[] s = iSrcData[b];
                    int[] d = iDstData[b];

                    int heightEnd = dstBandOffsets[b] + height;

                    for (int dstLineOffset = dstBandOffsets[b], srcLineOffset = srcBandOffsets[b];
                            dstLineOffset < heightEnd;
                            dstLineOffset += dstLineStride, srcLineOffset += srcLineStride) {

                        int widthEnd = dstLineOffset + width;

                        for (int dstPixelOffset = dstLineOffset, srcPixelOffset = srcLineOffset;
                                dstPixelOffset < widthEnd;
                                dstPixelOffset += dstPixelStride, srcPixelOffset += srcPixelStride) {

                            d[dstPixelOffset] = s[srcPixelOffset];
                        }
                    }
                }
                break;

            case DataBuffer.TYPE_FLOAT:
                float[][] fSrcData = src.getFloatDataArrays();
                float[][] fDstData = dst.getFloatDataArrays();

                for (int b = 0; b < bands; b++) {
                    float[] s = fSrcData[b];
                    float[] d = fDstData[b];

                    int heightEnd = dstBandOffsets[b] + height;

                    for (int dstLineOffset = dstBandOffsets[b], srcLineOffset = srcBandOffsets[b];
                            dstLineOffset < heightEnd;
                            dstLineOffset += dstLineStride, srcLineOffset += srcLineStride) {

                        int widthEnd = dstLineOffset + width;

                        for (int dstPixelOffset = dstLineOffset, srcPixelOffset = srcLineOffset;
                                dstPixelOffset < widthEnd;
                                dstPixelOffset += dstPixelStride, srcPixelOffset += srcPixelStride) {

                            d[dstPixelOffset] = s[srcPixelOffset];
                        }
                    }
                }
                break;

            case DataBuffer.TYPE_DOUBLE:
                double[][] dSrcData = src.getDoubleDataArrays();
                double[][] dDstData = dst.getDoubleDataArrays();

                for (int b = 0; b < bands; b++) {
                    double[] s = dSrcData[b];
                    double[] d = dDstData[b];

                    int heightEnd = dstBandOffsets[b] + height;

                    for (int dstLineOffset = dstBandOffsets[b], srcLineOffset = srcBandOffsets[b];
                            dstLineOffset < heightEnd;
                            dstLineOffset += dstLineStride, srcLineOffset += srcLineStride) {

                        int widthEnd = dstLineOffset + width;

                        for (int dstPixelOffset = dstLineOffset, srcPixelOffset = srcLineOffset;
                                dstPixelOffset < widthEnd;
                                dstPixelOffset += dstPixelStride, srcPixelOffset += srcPixelStride) {

                            d[dstPixelOffset] = s[srcPixelOffset];
                        }
                    }
                }
                break;
        }

        if (dst.isDataCopy()) {
            dst.clampDataArrays();
            dst.copyDataToRaster();
        }
    }

    /**
     * Determines whether two SampleModels are "equal", i.e., assignment-compatible. This signifies that the two
     * SampleModels are either the very same object or are two different objects with identical characteristics.
     */
    public boolean areEqualSampleModels(SampleModel sm1, SampleModel sm2) {
        if (sm1 == sm2) {
            // Identical objects.
            return true;
        } else if (sm1.getClass() == sm2.getClass()
                && sm1.getDataType() == sm2.getDataType()
                && sm1.getTransferType() == sm2.getTransferType()
                && sm1.getWidth() == sm2.getWidth()
                && sm1.getHeight() == sm2.getHeight()) {
            // At this point all common attributes are equivalent. Next test
            // those specific to the known direct subclasses of SampleModel.
            // Subclasses which are not known will always return false.
            if (sm1 instanceof ComponentSampleModel) {
                ComponentSampleModel csm1 = (ComponentSampleModel) sm1;
                ComponentSampleModel csm2 = (ComponentSampleModel) sm2;
                return csm1.getPixelStride() == csm2.getPixelStride()
                        && csm1.getScanlineStride() == csm2.getScanlineStride()
                        && Arrays.equals(csm1.getBankIndices(), csm2.getBankIndices())
                        && Arrays.equals(csm1.getBandOffsets(), csm2.getBandOffsets());
            } else if (sm1 instanceof MultiPixelPackedSampleModel) {
                MultiPixelPackedSampleModel mpp1 = (MultiPixelPackedSampleModel) sm1;
                MultiPixelPackedSampleModel mpp2 = (MultiPixelPackedSampleModel) sm2;
                return mpp1.getPixelBitStride() == mpp2.getPixelBitStride()
                        && mpp1.getScanlineStride() == mpp2.getScanlineStride()
                        && mpp1.getDataBitOffset() == mpp2.getDataBitOffset();
            } else if (sm1 instanceof SinglePixelPackedSampleModel) {
                SinglePixelPackedSampleModel spp1 = (SinglePixelPackedSampleModel) sm1;
                SinglePixelPackedSampleModel spp2 = (SinglePixelPackedSampleModel) sm2;
                return spp1.getScanlineStride() == spp2.getScanlineStride()
                        && Arrays.equals(spp1.getBitMasks(), spp2.getBitMasks());
            }
        }

        return false;
    }

    /// ---- BEGIN Binary data handling methods ----

    /**
     * Check whether a <code>SampleModel</code> represents a binary data set, i.e., a single band of data with one bit
     * per pixel packed into a <code>MultiPixelPackedSampleModel</code>.
     */
    public static boolean isBinary(SampleModel sm) {
        return sm instanceof MultiPixelPackedSampleModel
                && ((MultiPixelPackedSampleModel) sm).getPixelBitStride() == 1
                && sm.getNumBands() == 1;
    }

    /**
     * For the case of binary data (<code>isBinary()</code> returns <code>true</code>), return the binary data as a
     * packed byte array. The data will be packed as eight bits per byte with no bit offset, i.e., the first bit in each
     * image line will be the left-most of the first byte of the line. The line stride in bytes will be <code>
     * (int)((getWidth()+7)/8)</code>. The length of the returned array will be the line stride multiplied by <code>
     * getHeight()</code>
     *
     * @return the binary data as a packed array of bytes with zero offset of <code>null</code> if the data are not
     *     binary.
     * @throws IllegalArgumentException if <code>isBinary()</code> returns <code>false</code> with the <code>SampleModel
     *     </code> of the supplied <code>Raster</code> as argument.
     */
    public static byte[] getPackedBinaryData(Raster raster, Rectangle rect) {
        SampleModel sm = raster.getSampleModel();
        if (!isBinary(sm)) {
            throw new IllegalArgumentException(JaiI18N.getString("ImageUtil0"));
        }

        int rectX = rect.x;
        int rectY = rect.y;
        int rectWidth = rect.width;
        int rectHeight = rect.height;

        DataBuffer dataBuffer = raster.getDataBuffer();

        int dx = rectX - raster.getSampleModelTranslateX();
        int dy = rectY - raster.getSampleModelTranslateY();

        MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel) sm;
        int lineStride = mpp.getScanlineStride();
        int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
        int bitOffset = mpp.getBitOffset(dx);

        int numBytesPerRow = (rectWidth + 7) / 8;
        if (dataBuffer instanceof DataBufferByte
                && eltOffset == 0
                && bitOffset == 0
                && numBytesPerRow == lineStride
                && ((DataBufferByte) dataBuffer).getData().length == numBytesPerRow * rectHeight) {
            return ((DataBufferByte) dataBuffer).getData();
        }

        byte[] binaryDataArray = new byte[numBytesPerRow * rectHeight];

        int b = 0;

        if (bitOffset == 0) {
            if (dataBuffer instanceof DataBufferByte) {
                byte[] data = ((DataBufferByte) dataBuffer).getData();
                int stride = numBytesPerRow;
                int offset = 0;
                for (int y = 0; y < rectHeight; y++) {
                    System.arraycopy(data, eltOffset, binaryDataArray, offset, stride);
                    offset += stride;
                    eltOffset += lineStride;
                }
            } else if (dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) {
                short[] data = dataBuffer instanceof DataBufferShort
                        ? ((DataBufferShort) dataBuffer).getData()
                        : ((DataBufferUShort) dataBuffer).getData();

                for (int y = 0; y < rectHeight; y++) {
                    int xRemaining = rectWidth;
                    int i = eltOffset;
                    while (xRemaining > 8) {
                        short datum = data[i++];
                        binaryDataArray[b++] = (byte) ((datum >>> 8) & 0xFF);
                        binaryDataArray[b++] = (byte) (datum & 0xFF);
                        xRemaining -= 16;
                    }
                    if (xRemaining > 0) {
                        binaryDataArray[b++] = (byte) ((data[i] >>> 8) & 0XFF);
                    }
                    eltOffset += lineStride;
                }
            } else if (dataBuffer instanceof DataBufferInt) {
                int[] data = ((DataBufferInt) dataBuffer).getData();

                for (int y = 0; y < rectHeight; y++) {
                    int xRemaining = rectWidth;
                    int i = eltOffset;
                    while (xRemaining > 24) {
                        int datum = data[i++];
                        binaryDataArray[b++] = (byte) ((datum >>> 24) & 0xFF);
                        binaryDataArray[b++] = (byte) ((datum >>> 16) & 0xFF);
                        binaryDataArray[b++] = (byte) ((datum >>> 8) & 0xFF);
                        binaryDataArray[b++] = (byte) (datum & 0xFF);
                        xRemaining -= 32;
                    }
                    int shift = 24;
                    while (xRemaining > 0) {
                        binaryDataArray[b++] = (byte) ((data[i] >>> shift) & 0xFF);
                        shift -= 8;
                        xRemaining -= 8;
                    }
                    eltOffset += lineStride;
                }
            }
        } else { // bitOffset != 0
            if (dataBuffer instanceof DataBufferByte) {
                byte[] data = ((DataBufferByte) dataBuffer).getData();

                if ((bitOffset & 7) == 0) {
                    int stride = numBytesPerRow;
                    int offset = 0;
                    for (int y = 0; y < rectHeight; y++) {
                        System.arraycopy(data, eltOffset, binaryDataArray, offset, stride);
                        offset += stride;
                        eltOffset += lineStride;
                    }
                } else { // bitOffset % 8 != 0
                    int leftShift = bitOffset & 7;
                    int rightShift = 8 - leftShift;
                    for (int y = 0; y < rectHeight; y++) {
                        int i = eltOffset;
                        int xRemaining = rectWidth;
                        while (xRemaining > 0) {
                            if (xRemaining > rightShift) {
                                binaryDataArray[b++] =
                                        (byte) (((data[i++] & 0xFF) << leftShift) | ((data[i] & 0xFF) >>> rightShift));
                            } else {
                                binaryDataArray[b++] = (byte) ((data[i] & 0xFF) << leftShift);
                            }
                            xRemaining -= 8;
                        }
                        eltOffset += lineStride;
                    }
                }
            } else if (dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) {
                short[] data = dataBuffer instanceof DataBufferShort
                        ? ((DataBufferShort) dataBuffer).getData()
                        : ((DataBufferUShort) dataBuffer).getData();

                for (int y = 0; y < rectHeight; y++) {
                    int bOffset = bitOffset;
                    for (int x = 0; x < rectWidth; x += 8, bOffset += 8) {
                        int i = eltOffset + bOffset / 16;
                        int mod = bOffset % 16;
                        int left = data[i] & 0xFFFF;
                        if (mod <= 8) {
                            binaryDataArray[b++] = (byte) (left >>> (8 - mod));
                        } else {
                            int delta = mod - 8;
                            int right = data[i + 1] & 0xFFFF;
                            binaryDataArray[b++] = (byte) ((left << delta) | (right >>> (16 - delta)));
                        }
                    }
                    eltOffset += lineStride;
                }
            } else if (dataBuffer instanceof DataBufferInt) {
                int[] data = ((DataBufferInt) dataBuffer).getData();

                for (int y = 0; y < rectHeight; y++) {
                    int bOffset = bitOffset;
                    for (int x = 0; x < rectWidth; x += 8, bOffset += 8) {
                        int i = eltOffset + bOffset / 32;
                        int mod = bOffset % 32;
                        int left = data[i];
                        if (mod <= 24) {
                            binaryDataArray[b++] = (byte) (left >>> (24 - mod));
                        } else {
                            int delta = mod - 24;
                            int right = data[i + 1];
                            binaryDataArray[b++] = (byte) ((left << delta) | (right >>> (32 - delta)));
                        }
                    }
                    eltOffset += lineStride;
                }
            }
        }

        return binaryDataArray;
    }

    /**
     * Returns the binary data unpacked into an array of bytes. The line stride will be the width of the <code>Raster
     * </code>.
     *
     * @throws IllegalArgumentException if <code>isBinary()</code> returns <code>false</code> with the <code>SampleModel
     *     </code> of the supplied <code>Raster</code> as argument.
     */
    public static byte[] getUnpackedBinaryData(Raster raster, Rectangle rect) {
        SampleModel sm = raster.getSampleModel();
        if (!isBinary(sm)) {
            throw new IllegalArgumentException(JaiI18N.getString("ImageUtil0"));
        }

        int rectX = rect.x;
        int rectY = rect.y;
        int rectWidth = rect.width;
        int rectHeight = rect.height;

        DataBuffer dataBuffer = raster.getDataBuffer();

        int dx = rectX - raster.getSampleModelTranslateX();
        int dy = rectY - raster.getSampleModelTranslateY();

        MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel) sm;
        int lineStride = mpp.getScanlineStride();
        int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
        int bitOffset = mpp.getBitOffset(dx);

        byte[] bdata = new byte[rectWidth * rectHeight];
        int maxY = rectY + rectHeight;
        int maxX = rectX + rectWidth;
        int k = 0;

        if (dataBuffer instanceof DataBufferByte) {
            byte[] data = ((DataBufferByte) dataBuffer).getData();
            for (int y = rectY; y < maxY; y++) {
                int bOffset = eltOffset * 8 + bitOffset;
                for (int x = rectX; x < maxX; x++) {
                    byte b = data[bOffset / 8];
                    bdata[k++] = (byte) ((b >>> (7 - bOffset & 7)) & 0x0000001);
                    bOffset++;
                }
                eltOffset += lineStride;
            }
        } else if (dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) {
            short[] data = dataBuffer instanceof DataBufferShort
                    ? ((DataBufferShort) dataBuffer).getData()
                    : ((DataBufferUShort) dataBuffer).getData();
            for (int y = rectY; y < maxY; y++) {
                int bOffset = eltOffset * 16 + bitOffset;
                for (int x = rectX; x < maxX; x++) {
                    short s = data[bOffset / 16];
                    bdata[k++] = (byte) ((s >>> (15 - bOffset % 16)) & 0x0000001);
                    bOffset++;
                }
                eltOffset += lineStride;
            }
        } else if (dataBuffer instanceof DataBufferInt) {
            int[] data = ((DataBufferInt) dataBuffer).getData();
            for (int y = rectY; y < maxY; y++) {
                int bOffset = eltOffset * 32 + bitOffset;
                for (int x = rectX; x < maxX; x++) {
                    int i = data[bOffset / 32];
                    bdata[k++] = (byte) ((i >>> (31 - bOffset % 32)) & 0x0000001);
                    bOffset++;
                }
                eltOffset += lineStride;
            }
        }

        return bdata;
    }

    /**
     * Sets the supplied <code>Raster</code>'s data from an array of packed binary data of the form returned by <code>
     * getPackedBinaryData()</code>.
     *
     * @throws IllegalArgumentException if <code>isBinary()</code> returns <code>false</code> with the <code>SampleModel
     *     </code> of the supplied <code>Raster</code> as argument.
     */
    public static void setPackedBinaryData(byte[] binaryDataArray, WritableRaster raster, Rectangle rect) {
        SampleModel sm = raster.getSampleModel();
        if (!isBinary(sm)) {
            throw new IllegalArgumentException(JaiI18N.getString("ImageUtil0"));
        }

        int rectX = rect.x;
        int rectY = rect.y;
        int rectWidth = rect.width;
        int rectHeight = rect.height;

        DataBuffer dataBuffer = raster.getDataBuffer();

        int dx = rectX - raster.getSampleModelTranslateX();
        int dy = rectY - raster.getSampleModelTranslateY();

        MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel) sm;
        int lineStride = mpp.getScanlineStride();
        int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
        int bitOffset = mpp.getBitOffset(dx);

        int b = 0;

        if (bitOffset == 0) {
            if (dataBuffer instanceof DataBufferByte) {
                byte[] data = ((DataBufferByte) dataBuffer).getData();
                if (data == binaryDataArray) {
                    // Optimal case: simply return.
                    return;
                }
                int stride = (rectWidth + 7) / 8;
                int offset = 0;
                for (int y = 0; y < rectHeight; y++) {
                    System.arraycopy(binaryDataArray, offset, data, eltOffset, stride);
                    offset += stride;
                    eltOffset += lineStride;
                }
            } else if (dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) {
                short[] data = dataBuffer instanceof DataBufferShort
                        ? ((DataBufferShort) dataBuffer).getData()
                        : ((DataBufferUShort) dataBuffer).getData();

                for (int y = 0; y < rectHeight; y++) {
                    int xRemaining = rectWidth;
                    int i = eltOffset;
                    while (xRemaining > 8) {
                        data[i++] = (short) (((binaryDataArray[b++] & 0xFF) << 8) | (binaryDataArray[b++] & 0xFF));
                        xRemaining -= 16;
                    }
                    if (xRemaining > 0) {
                        data[i++] = (short) ((binaryDataArray[b++] & 0xFF) << 8);
                    }
                    eltOffset += lineStride;
                }
            } else if (dataBuffer instanceof DataBufferInt) {
                int[] data = ((DataBufferInt) dataBuffer).getData();

                for (int y = 0; y < rectHeight; y++) {
                    int xRemaining = rectWidth;
                    int i = eltOffset;
                    while (xRemaining > 24) {
                        data[i++] = (int) (((binaryDataArray[b++] & 0xFF) << 24)
                                | ((binaryDataArray[b++] & 0xFF) << 16)
                                | ((binaryDataArray[b++] & 0xFF) << 8)
                                | (binaryDataArray[b++] & 0xFF));
                        xRemaining -= 32;
                    }
                    int shift = 24;
                    while (xRemaining > 0) {
                        data[i] |= (int) ((binaryDataArray[b++] & 0xFF) << shift);
                        shift -= 8;
                        xRemaining -= 8;
                    }
                    eltOffset += lineStride;
                }
            }
        } else { // bitOffset != 0
            int stride = (rectWidth + 7) / 8;
            int offset = 0;
            if (dataBuffer instanceof DataBufferByte) {
                byte[] data = ((DataBufferByte) dataBuffer).getData();

                if ((bitOffset & 7) == 0) {
                    for (int y = 0; y < rectHeight; y++) {
                        System.arraycopy(binaryDataArray, offset, data, eltOffset, stride);
                        offset += stride;
                        eltOffset += lineStride;
                    }
                } else { // bitOffset % 8 != 0
                    int rightShift = bitOffset & 7;
                    int leftShift = 8 - rightShift;
                    int leftShift8 = 8 + leftShift;
                    int mask = (byte) (255 << leftShift);
                    int mask1 = (byte) ~mask;

                    for (int y = 0; y < rectHeight; y++) {
                        int i = eltOffset;
                        int xRemaining = rectWidth;
                        while (xRemaining > 0) {
                            byte datum = binaryDataArray[b++];

                            if (xRemaining > leftShift8) {
                                // when all the bits in this BYTE will be set
                                // into the data buffer.
                                data[i] = (byte) ((data[i] & mask) | ((datum & 0xFF) >>> rightShift));
                                data[++i] = (byte) ((datum & 0xFF) << leftShift);
                            } else if (xRemaining > leftShift) {
                                // All the "leftShift" high bits will be set
                                // into the data buffer.  But not all the
                                // "rightShift" low bits will be set.
                                data[i] = (byte) ((data[i] & mask) | ((datum & 0xFF) >>> rightShift));
                                i++;
                                data[i] = (byte) ((data[i] & mask1) | ((datum & 0xFF) << leftShift));
                            } else {
                                // Less than "leftShift" high bits will be set.
                                int remainMask = (1 << leftShift - xRemaining) - 1;
                                data[i] = (byte)
                                        ((data[i] & (mask | remainMask)) | (datum & 0xFF) >>> rightShift & ~remainMask);
                            }
                            xRemaining -= 8;
                        }
                        eltOffset += lineStride;
                    }
                }
            } else if (dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) {
                short[] data = dataBuffer instanceof DataBufferShort
                        ? ((DataBufferShort) dataBuffer).getData()
                        : ((DataBufferUShort) dataBuffer).getData();

                int rightShift = bitOffset & 7;
                int leftShift = 8 - rightShift;
                int leftShift16 = 16 + leftShift;
                int mask = (short) (~(255 << leftShift));
                int mask1 = (short) (65535 << leftShift);
                int mask2 = (short) ~mask1;

                for (int y = 0; y < rectHeight; y++) {
                    int bOffset = bitOffset;
                    int xRemaining = rectWidth;
                    for (int x = 0; x < rectWidth; x += 8, bOffset += 8, xRemaining -= 8) {
                        int i = eltOffset + (bOffset >> 4);
                        int mod = bOffset & 15;
                        int datum = binaryDataArray[b++] & 0xFF;
                        if (mod <= 8) {
                            // This BYTE is set into one SHORT
                            if (xRemaining < 8) {
                                // Mask the bits to be set.
                                datum &= 255 << 8 - xRemaining;
                            }
                            data[i] = (short) ((data[i] & mask) | (datum << leftShift));
                        } else if (xRemaining > leftShift16) {
                            // This BYTE will be set into two SHORTs
                            data[i] = (short) ((data[i] & mask1) | ((datum >>> rightShift) & 0xFFFF));
                            data[++i] = (short) ((datum << leftShift) & 0xFFFF);
                        } else if (xRemaining > leftShift) {
                            // This BYTE will be set into two SHORTs;
                            // But not all the low bits will be set into SHORT
                            data[i] = (short) ((data[i] & mask1) | ((datum >>> rightShift) & 0xFFFF));
                            i++;
                            data[i] = (short) ((data[i] & mask2) | ((datum << leftShift) & 0xFFFF));
                        } else {
                            // Only some of the high bits will be set into
                            // SHORTs
                            int remainMask = (1 << leftShift - xRemaining) - 1;
                            data[i] = (short) ((data[i] & (mask1 | remainMask))
                                    | ((datum >>> rightShift) & 0xFFFF & ~remainMask));
                        }
                    }
                    eltOffset += lineStride;
                }
            } else if (dataBuffer instanceof DataBufferInt) {
                int[] data = ((DataBufferInt) dataBuffer).getData();
                int rightShift = bitOffset & 7;
                int leftShift = 8 - rightShift;
                int leftShift32 = 32 + leftShift;
                int mask = 0xFFFFFFFF << leftShift;
                int mask1 = ~mask;

                for (int y = 0; y < rectHeight; y++) {
                    int bOffset = bitOffset;
                    int xRemaining = rectWidth;
                    for (int x = 0; x < rectWidth; x += 8, bOffset += 8, xRemaining -= 8) {
                        int i = eltOffset + (bOffset >> 5);
                        int mod = bOffset & 31;
                        int datum = binaryDataArray[b++] & 0xFF;
                        if (mod <= 24) {
                            // This BYTE is set into one INT
                            int shift = 24 - mod;
                            if (xRemaining < 8) {
                                // Mask the bits to be set.
                                datum &= 255 << 8 - xRemaining;
                            }
                            data[i] = (data[i] & (~(255 << shift))) | (datum << shift);
                        } else if (xRemaining > leftShift32) {
                            // All the bits of this BYTE will be set into two INTs
                            data[i] = (data[i] & mask) | (datum >>> rightShift);
                            data[++i] = datum << leftShift;
                        } else if (xRemaining > leftShift) {
                            // This BYTE will be set into two INTs;
                            // But not all the low bits will be set into INT
                            data[i] = (data[i] & mask) | (datum >>> rightShift);
                            i++;
                            data[i] = (data[i] & mask1) | (datum << leftShift);
                        } else {
                            // Only some of the high bits will be set into INT
                            int remainMask = (1 << leftShift - xRemaining) - 1;
                            data[i] = (data[i] & (mask | remainMask)) | (datum >>> rightShift & ~remainMask);
                        }
                    }
                    eltOffset += lineStride;
                }
            }
        }
    }

    /**
     * Copies data into the packed array of the <code>Raster</code> from an array of unpacked data of the form returned
     * by <code>getUnpackedBinaryData()</code>.
     *
     * <p>If the data are binary, then the target bit will be set if and only if the corresponding byte is non-zero.
     *
     * @throws IllegalArgumentException if <code>isBinary()</code> returns <code>false</code> with the <code>SampleModel
     *     </code> of the supplied <code>Raster</code> as argument.
     */
    public static void setUnpackedBinaryData(byte[] bdata, WritableRaster raster, Rectangle rect) {
        SampleModel sm = raster.getSampleModel();
        if (!isBinary(sm)) {
            throw new IllegalArgumentException(JaiI18N.getString("ImageUtil0"));
        }

        int rectX = rect.x;
        int rectY = rect.y;
        int rectWidth = rect.width;
        int rectHeight = rect.height;

        DataBuffer dataBuffer = raster.getDataBuffer();

        int dx = rectX - raster.getSampleModelTranslateX();
        int dy = rectY - raster.getSampleModelTranslateY();

        MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel) sm;
        int lineStride = mpp.getScanlineStride();
        int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
        int bitOffset = mpp.getBitOffset(dx);

        int k = 0;

        if (dataBuffer instanceof DataBufferByte) {
            byte[] data = ((DataBufferByte) dataBuffer).getData();
            for (int y = 0; y < rectHeight; y++) {
                int bOffset = eltOffset * 8 + bitOffset;
                for (int x = 0; x < rectWidth; x++) {
                    if (bdata[k++] != (byte) 0) {
                        data[bOffset / 8] |= (byte) (0x00000001 << (7 - bOffset & 7));
                    }
                    bOffset++;
                }
                eltOffset += lineStride;
            }
        } else if (dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) {
            short[] data = dataBuffer instanceof DataBufferShort
                    ? ((DataBufferShort) dataBuffer).getData()
                    : ((DataBufferUShort) dataBuffer).getData();
            for (int y = 0; y < rectHeight; y++) {
                int bOffset = eltOffset * 16 + bitOffset;
                for (int x = 0; x < rectWidth; x++) {
                    if (bdata[k++] != (byte) 0) {
                        data[bOffset / 16] |= (short) (0x00000001 << (15 - bOffset % 16));
                    }
                    bOffset++;
                }
                eltOffset += lineStride;
            }
        } else if (dataBuffer instanceof DataBufferInt) {
            int[] data = ((DataBufferInt) dataBuffer).getData();
            for (int y = 0; y < rectHeight; y++) {
                int bOffset = eltOffset * 32 + bitOffset;
                for (int x = 0; x < rectWidth; x++) {
                    if (bdata[k++] != (byte) 0) {
                        data[bOffset / 32] |= (int) (0x00000001 << (31 - bOffset % 32));
                    }
                    bOffset++;
                }
                eltOffset += lineStride;
            }
        }
    }

    /**
     * Fill the specified rectangle of <code>raster</code> with the provided background values. Suppose the raster is
     * initialized to 0. Thus, for binary data, if the provided background values are 0, do nothing.
     */
    public static void fillBackground(WritableRaster raster, Rectangle rect, double[] backgroundValues) {
        rect = rect.intersection(raster.getBounds());
        int numBands = raster.getSampleModel().getNumBands();
        SampleModel sm = raster.getSampleModel();
        PixelAccessor accessor = new PixelAccessor(sm, null);

        if (isBinary(sm)) {
            // fill binary data
            byte value = (byte) (((int) backgroundValues[0]) & 1);
            if (value == 0) return;
            int rectX = rect.x;
            int rectY = rect.y;
            int rectWidth = rect.width;
            int rectHeight = rect.height;

            int dx = rectX - raster.getSampleModelTranslateX();
            int dy = rectY - raster.getSampleModelTranslateY();

            DataBuffer dataBuffer = raster.getDataBuffer();
            MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel) sm;
            int lineStride = mpp.getScanlineStride();
            int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
            int bitOffset = mpp.getBitOffset(dx);

            switch (sm.getDataType()) {
                case DataBuffer.TYPE_BYTE: {
                    byte[] data = ((DataBufferByte) dataBuffer).getData();
                    int bits = bitOffset & 7;
                    int otherBits = (bits == 0) ? 0 : 8 - bits;

                    byte mask = (byte) (255 >> bits);
                    int lineLength = (rectWidth - otherBits) / 8;
                    int bits1 = (rectWidth - otherBits) & 7;
                    byte mask1 = (byte) (255 << (8 - bits1));
                    // If operating within a single byte, merge masks into one
                    // and don't apply second mask after while loop
                    if (lineLength == 0) {
                        mask &= mask1;
                        bits1 = 0;
                    }

                    for (int y = 0; y < rectHeight; y++) {
                        int start = eltOffset;
                        int end = start + lineLength;
                        if (bits != 0) data[start++] |= mask;
                        while (start < end) data[start++] = (byte) 255;
                        if (bits1 != 0) data[start] |= mask1;
                        eltOffset += lineStride;
                    }
                    break;
                }
                case DataBuffer.TYPE_USHORT: {
                    short[] data = ((DataBufferUShort) dataBuffer).getData();
                    int bits = bitOffset & 15;
                    int otherBits = (bits == 0) ? 0 : 16 - bits;

                    short mask = (short) (65535 >> bits);
                    int lineLength = (rectWidth - otherBits) / 16;
                    int bits1 = (rectWidth - otherBits) & 15;
                    short mask1 = (short) (65535 << (16 - bits1));
                    // If operating within a single byte, merge masks into one
                    // and don't apply second mask after while loop
                    if (lineLength == 0) {
                        mask &= mask1;
                        bits1 = 0;
                    }

                    for (int y = 0; y < rectHeight; y++) {
                        int start = eltOffset;
                        int end = start + lineLength;
                        if (bits != 0) data[start++] |= mask;
                        while (start < end) data[start++] = (short) 0xFFFF;
                        if (bits1 != 0) data[start++] |= mask1;
                        eltOffset += lineStride;
                    }
                    break;
                }
                case DataBuffer.TYPE_INT: {
                    int[] data = ((DataBufferInt) dataBuffer).getData();
                    int bits = bitOffset & 31;
                    int otherBits = (bits == 0) ? 0 : 32 - bits;

                    int mask = 0xFFFFFFFF >> bits;
                    int lineLength = (rectWidth - otherBits) / 32;
                    int bits1 = (rectWidth - otherBits) & 31;
                    int mask1 = 0xFFFFFFFF << (32 - bits1);
                    // If operating within a single byte, merge masks into one
                    // and don't apply second mask after while loop
                    if (lineLength == 0) {
                        mask &= mask1;
                        bits1 = 0;
                    }

                    for (int y = 0; y < rectHeight; y++) {
                        int start = eltOffset;
                        int end = start + lineLength;
                        if (bits != 0) data[start++] |= mask;
                        while (start < end) data[start++] = 0xFFFFFFFF;
                        if (bits1 != 0) data[start++] |= mask1;
                        eltOffset += lineStride;
                    }
                    break;
                }
            }
        } else {
            int srcSampleType =
                    accessor.sampleType == PixelAccessor.TYPE_BIT ? DataBuffer.TYPE_BYTE : accessor.sampleType;
            UnpackedImageData uid = accessor.getPixels(raster, rect, srcSampleType, false);
            rect = uid.rect;
            int lineStride = uid.lineStride;
            int pixelStride = uid.pixelStride;

            switch (uid.type) {
                case DataBuffer.TYPE_BYTE:
                    byte[][] bdata = uid.getByteData();
                    for (int b = 0; b < accessor.numBands; b++) {
                        byte value = (byte) backgroundValues[b];
                        byte[] bd = bdata[b];
                        int lastLine = uid.bandOffsets[b] + rect.height * lineStride;

                        for (int lo = uid.bandOffsets[b]; lo < lastLine; lo += lineStride) {
                            int lastPixel = lo + rect.width * pixelStride;
                            for (int po = lo; po < lastPixel; po += pixelStride) {
                                bd[po] = value;
                            }
                        }
                    }
                    break;
                case DataBuffer.TYPE_USHORT:
                case DataBuffer.TYPE_SHORT:
                    short[][] sdata = uid.getShortData();
                    for (int b = 0; b < accessor.numBands; b++) {
                        short value = (short) backgroundValues[b];
                        short[] sd = sdata[b];
                        int lastLine = uid.bandOffsets[b] + rect.height * lineStride;

                        for (int lo = uid.bandOffsets[b]; lo < lastLine; lo += lineStride) {
                            int lastPixel = lo + rect.width * pixelStride;
                            for (int po = lo; po < lastPixel; po += pixelStride) {
                                sd[po] = value;
                            }
                        }
                    }
                    break;
                case DataBuffer.TYPE_INT:
                    int[][] idata = uid.getIntData();
                    for (int b = 0; b < accessor.numBands; b++) {
                        int value = (int) backgroundValues[b];
                        int[] id = idata[b];
                        int lastLine = uid.bandOffsets[b] + rect.height * lineStride;

                        for (int lo = uid.bandOffsets[b]; lo < lastLine; lo += lineStride) {
                            int lastPixel = lo + rect.width * pixelStride;
                            for (int po = lo; po < lastPixel; po += pixelStride) {
                                id[po] = value;
                            }
                        }
                    }
                    break;
                case DataBuffer.TYPE_FLOAT:
                    float[][] fdata = uid.getFloatData();
                    for (int b = 0; b < accessor.numBands; b++) {
                        float value = (float) backgroundValues[b];
                        float[] fd = fdata[b];
                        int lastLine = uid.bandOffsets[b] + rect.height * lineStride;

                        for (int lo = uid.bandOffsets[b]; lo < lastLine; lo += lineStride) {
                            int lastPixel = lo + rect.width * pixelStride;
                            for (int po = lo; po < lastPixel; po += pixelStride) {
                                fd[po] = value;
                            }
                        }
                    }
                    break;
                case DataBuffer.TYPE_DOUBLE:
                    double[][] ddata = uid.getDoubleData();
                    for (int b = 0; b < accessor.numBands; b++) {
                        double value = backgroundValues[b];
                        double[] dd = ddata[b];
                        int lastLine = uid.bandOffsets[b] + rect.height * lineStride;

                        for (int lo = uid.bandOffsets[b]; lo < lastLine; lo += lineStride) {
                            int lastPixel = lo + rect.width * pixelStride;
                            for (int po = lo; po < lastPixel; po += pixelStride) {
                                dd[po] = value;
                            }
                        }
                    }
                    break;
            }
        }
    }

    /** When the destination rectangle is not the same as the image bounds, should fill the border. */
    public static void fillBordersWithBackgroundValues(
            Rectangle outerRect, Rectangle innerRect, WritableRaster raster, double[] backgroundValues) {
        int outerMaxX = outerRect.x + outerRect.width;
        int outerMaxY = outerRect.y + outerRect.height;

        int innerMaxX = innerRect.x + innerRect.width;
        int innerMaxY = innerRect.y + innerRect.height;

        if (outerRect.x < innerRect.x) {
            Rectangle rect =
                    new Rectangle(outerRect.x, innerRect.y, innerRect.x - outerRect.x, outerMaxY - innerRect.y);
            fillBackground(raster, rect, backgroundValues);
        }

        if (outerRect.y < innerRect.y) {
            Rectangle rect =
                    new Rectangle(outerRect.x, outerRect.y, innerMaxX - outerRect.x, innerRect.y - outerRect.y);
            fillBackground(raster, rect, backgroundValues);
        }

        if (outerMaxX > innerMaxX) {
            Rectangle rect = new Rectangle(innerMaxX, outerRect.y, outerMaxX - innerMaxX, innerMaxY - outerRect.y);
            fillBackground(raster, rect, backgroundValues);
        }

        if (outerMaxY > innerMaxY) {
            Rectangle rect = new Rectangle(innerRect.x, innerMaxY, outerMaxX - innerRect.x, outerMaxY - innerMaxY);
            fillBackground(raster, rect, backgroundValues);
        }
    }

    /// ---- END Binary data handling methods ----

    /**
     * Given a kernel and the gain (sharpness) factor of an UnsharpMask operation, compute a modified kernel that would
     * be equivalent to the specified unsharp operation.
     *
     * <p>for UnsharpMask function we have the following formula:
     *
     * <p>dst(i,j) = src(i,j) + gain * (src(i,j) - SUM SUM K(l,m) * src(i+l,j+m)) l m
     *
     * <p>Which can be written as :
     *
     * <p>dst(i,j) = SUM SUM Q(l,m) * src(i+l,j+m), l m
     *
     * <p>where Q(0,0) = 1 + gain * (1 - K(0,0)), and Q(l,m) = - gain * K(l,m) otherwise
     *
     * @param kernel the unsharp mask kernel
     * @param gain the unsharp mask gain (sharpness) factor.
     * @return an equivalent convolution KernelJAI
     */
    public static KernelJAI getUnsharpMaskEquivalentKernel(KernelJAI kernel, float gain) {

        int width = kernel.getWidth();
        int height = kernel.getHeight();
        int xOrigin = kernel.getXOrigin();
        int yOrigin = kernel.getYOrigin();

        float oldData[] = kernel.getKernelData();
        float newData[] = new float[oldData.length];

        int k;

        for (k = 0; k < width * height; k++) newData[k] = -gain * oldData[k];

        k = yOrigin * width + xOrigin;
        newData[k] = 1.0f + gain * (1.0f - oldData[k]);

        return new KernelJAI(width, height, xOrigin, yOrigin, newData);
    }

    /** Retrieve the indices of a set of tiles in row-major order with the given tile index bounds in x and y. */
    public static final Point[] getTileIndices(int txmin, int txmax, int tymin, int tymax) {
        if (txmin > txmax || tymin > tymax) {
            return null;
        }

        Point[] tileIndices = new Point[(txmax - txmin + 1) * (tymax - tymin + 1)];
        int k = 0;
        for (int tj = tymin; tj <= tymax; tj++) {
            for (int ti = txmin; ti <= txmax; ti++) {
                tileIndices[k++] = new Point(ti, tj);
            }
        }

        return tileIndices;
    }

    /// Method for handling DeferrdData objects in ParameterBlocks.

    /**
     * If any <code>DeferredData</code> components are detected, the argument is cloned and the <code>DeferredData
     * </code> object is replaced with what its <code>getData()</code> returns.
     */
    public static Vector evaluateParameters(Vector parameters) {
        if (parameters == null) {
            throw new IllegalArgumentException();
        }

        Vector paramEval = parameters;

        int size = parameters.size();
        for (int i = 0; i < size; i++) {
            Object element = parameters.get(i);
            if (element instanceof DeferredData) {
                if (paramEval == parameters) {
                    paramEval = (Vector) parameters.clone();
                }
                paramEval.set(i, ((DeferredData) element).getData());
            }
        }

        return paramEval;
    }

    /**
     * If any <code>DeferredData</code> parameters are detected, a new <code>ParameterBlock</code> is constructed and
     * the <code>DeferredData</code> object is replaced with what its <code>getData()</code> returns.
     */
    public static ParameterBlock evaluateParameters(ParameterBlock pb) {
        if (pb == null) {
            throw new IllegalArgumentException();
        }

        Vector parameters = pb.getParameters();
        Vector paramEval = evaluateParameters(parameters);
        return paramEval == parameters ? pb : new ParameterBlock(pb.getSources(), paramEval);
    }

    /**
     * Derive a compatible <code>ColorModel</code> for the supplied <code>SampleModel</code> using the method specified
     * via the <code>OpImage</code> configuration <code>Map</code>.
     *
     * @return a compatible <code>ColorModel</code> or <code>null</code>.
     */
    public static ColorModel getCompatibleColorModel(SampleModel sm, Map config) {
        ColorModel cm = null;

        if (config == null || !Boolean.FALSE.equals(config.get(JAI.KEY_DEFAULT_COLOR_MODEL_ENABLED))) {

            // Set the default ColorModel

            if (config != null && config.containsKey(JAI.KEY_DEFAULT_COLOR_MODEL_METHOD)) {
                // Attempt to retrieve the default CM Method.
                Method cmMethod = (Method) config.get(JAI.KEY_DEFAULT_COLOR_MODEL_METHOD);

                // Check method compatibility.
                Class[] paramTypes = cmMethod.getParameterTypes();
                if ((cmMethod.getModifiers() & Modifier.STATIC) != Modifier.STATIC) {
                    // Method must be static.
                    throw new RuntimeException(JaiI18N.getString("ImageUtil1"));
                } else if (cmMethod.getReturnType() != ColorModel.class) {
                    // Method must return a ColorModel.
                    throw new RuntimeException(JaiI18N.getString("ImageUtil2"));
                } else if (paramTypes.length != 1 || !paramTypes[0].equals(SampleModel.class)) {
                    // Unique Method parameter must be a SampleModel.
                    throw new RuntimeException(JaiI18N.getString("ImageUtil3"));
                }

                // Set the default ColorModel.
                try {
                    // Attempt to use the supplied Method.
                    Object[] args = new Object[] {sm};
                    cm = (ColorModel) cmMethod.invoke(null, args);
                } catch (Exception e) {
                    String message = JaiI18N.getString("ImageUtil4") + cmMethod.getName();
                    sendExceptionToListener(message, new ImagingException(message, e));
                    /*
                                        // XXX Is this a reasonable Exception to throw?
                                        throw new RuntimeException(cmMethod.getName()+" "+
                                                                   e.getMessage());
                    */
                }
            } else { // No default method hint set.
                // Use PlanarImage method.
                cm = PlanarImage.createColorModel(sm);
            }
        }

        return cm;
    }

    /** Converts the supplied <code>Exception</code>'s stack trace to a <code>String</code>. */
    public static String getStackTraceString(Exception e) {
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        PrintStream printStream = new PrintStream(byteStream);
        e.printStackTrace(printStream);
        printStream.flush();
        String stackTraceString = byteStream.toString();
        printStream.close();
        return stackTraceString;
    }

    public static ImagingListener getImagingListener(RenderingHints hints) {
        ImagingListener listener = null;
        if (hints != null) listener = (ImagingListener) hints.get(JAI.KEY_IMAGING_LISTENER);

        if (listener == null) listener = JAI.getDefaultInstance().getImagingListener();
        return listener;
    }

    public static ImagingListener getImagingListener(RenderContext context) {
        return getImagingListener(context.getRenderingHints());
    }

    /**
     * Generates a UID for the provided <code>Object</code>. The counter for the objects that request an ID, the
     * hashcode of the class of the provided object, the hashcode of the provided object, the current time in milli
     * seconds, and a random number are concatenated together in a <code>BigInteger</code>. This <code>BigInteger</code>
     * is returned as the unique ID.
     */
    public static synchronized Object generateID(Object owner) {
        Class c = owner.getClass();
        counter++;

        byte[] uid = new byte[32];
        int k = 0;
        for (int i = 7, j = 0; i >= 0; i--, j += 8) uid[k++] = (byte) (counter >> j);
        int hash = c.hashCode();
        for (int i = 3, j = 0; i >= 0; i--, j += 8) uid[k++] = (byte) (hash >> j);
        hash = owner.hashCode();
        for (int i = 3, j = 0; i >= 0; i--, j += 8) uid[k++] = (byte) (hash >> j);
        long time = System.currentTimeMillis();
        for (int i = 7, j = 0; i >= 0; i--, j += 8) uid[k++] = (byte) (time >> j);
        long rand = Double.doubleToLongBits(new Double(Math.random()).doubleValue());
        for (int i = 7, j = 0; i >= 0; i--, j += 8) uid[k++] = (byte) (rand >> j);
        return new BigInteger(uid);
    }

    static void sendExceptionToListener(String message, Exception e) {
        ImagingListener listener = getImagingListener((RenderingHints) null);
        listener.errorOccurred(message, e, ImageUtil.class, false);
    }
}
